package fudan.se.project.service.advanced.conference.resource;


import fudan.se.project.controller.response.*;
import fudan.se.project.domain.User;
import fudan.se.project.domain.conference.Conference;
import fudan.se.project.domain.conference.Topic;
import fudan.se.project.domain.conference.post.Post;
import fudan.se.project.domain.conference.post.Rebuttal;
import fudan.se.project.domain.conference.production.Paper;
import fudan.se.project.exception.file.FileNotFoundException;
import fudan.se.project.repository.projection.PaperProjection;
import fudan.se.project.repository.projection.PostProjection;
import fudan.se.project.repository.projection.RebuttalProjection;
import fudan.se.project.repository.projection.customized.UnconfirmedPaperProjection;
import fudan.se.project.repository.projection.customized.UnconfirmedPaperProjection.Release;
import fudan.se.project.service.infrastructure.base.ConferencePermissionService;
import fudan.se.project.service.infrastructure.entity.conference.ConferenceService;
import fudan.se.project.service.infrastructure.entity.conference.TopicService;
import fudan.se.project.service.infrastructure.entity.conference.post.PostService;
import fudan.se.project.service.infrastructure.entity.conference.post.RebuttalService;
import fudan.se.project.service.infrastructure.entity.conference.production.PaperService;
import fudan.se.project.util.http.ResponseWrapperUtil;
import fudan.se.project.util.user.UserDetailsUtil;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;

import static fudan.se.project.repository.projection.ConferenceProjection.project;
import static fudan.se.project.repository.projection.summary.TopicSummary.summarize;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;

@Slf4j
@Service
@RequiredArgsConstructor
public class ConferenceResourceService {

    @NonNull
    private final ConferenceService conferenceService;

    @NonNull
    private final ConferencePermissionService conferencePermissionService;

    @NonNull
    private final ResponseWrapperUtil responseWrapperUtil;
    @NonNull
    private final UserDetailsUtil userDetailsUtil;
    @NonNull
    private final PaperService paperService;
    @NonNull
    private final TopicService topicService;

    @NonNull
    private final PostService postService;
    @NonNull
    private final RebuttalService rebuttalService;

    private static final String ARGUMENTS_ERROR = "arguments error";
    private static final String SUCCESS = "success";
    private static final String CONFERENCE_NOT_FOUND = "conference not found";
    private static final String PAPER = "paper ";


    /**
     * Queries a conference according to the conference id.
     *
     * @param id the conference id.
     */
    public ResponseEntity<?> getConference(long id) {

        Conference conference = conferenceService.findById(id);

        if (conference == null) {
            BaseResponse baseResponse = new BaseResponse(CONFERENCE_NOT_FOUND);
            return responseWrapperUtil.buildResponseBody(baseResponse, NOT_FOUND);
        } else {
            User user = userDetailsUtil.currentUser();

            List<String> roles = conferencePermissionService.fetchRoles(user, conferenceService.findById(id));
            ConferenceResponse conferenceResponse = new ConferenceResponse(project(conference), roles);
            conferenceResponse.setMessage(SUCCESS);

            return responseWrapperUtil.buildResponseBody(conferenceResponse, OK);
        }
    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'paper:any:'+#id)")
    public ResponseEntity<?> downloadPaper(long id) {
        Paper paper = paperService.findById(id);
        InputStreamResource inputStreamResource = paperService.downLoad(paper);
        if (inputStreamResource == null) {
            log.warn(PAPER + id + " not found. ");
            throw new FileNotFoundException(PAPER + id + " not found. ");
        } else {

            try {
                return ResponseEntity.ok()
                        .contentLength(inputStreamResource.getFile().length())
                        .contentType(APPLICATION_OCTET_STREAM)
                        .header(HttpHeaders.CONTENT_DISPOSITION,
                                "attachment; filename="
                                        + inputStreamResource.getFile().getName()
                        )
                        .body(inputStreamResource);
            } catch (IOException e) {
                throw new FileNotFoundException(PAPER + id + " read error. ");
            }
        }
    }

    public ResponseEntity<?> getConferenceTopics(long id) {

        List<Topic> topicList = topicService.findByConferenceId(id);
        if (topicList == null || topicList.isEmpty()) {
            BaseResponse baseResponse = new BaseResponse(ARGUMENTS_ERROR);
            return responseWrapperUtil.buildResponseBody(baseResponse, NOT_ACCEPTABLE);
        } else {
            TopicsGetResponse topicsGetResponse = new TopicsGetResponse(summarize(topicList));
            topicsGetResponse.setMessage(SUCCESS);
            return responseWrapperUtil.buildResponseBody(topicsGetResponse, OK);
        }
    }

    public ResponseEntity<?> getConferenceStage(long id) {
        Conference conference = conferenceService.findById(id);
        if (null == conference) {
            return responseWrapperUtil.
                    buildBaseResponseBody(CONFERENCE_NOT_FOUND, NOT_FOUND);
        }
        ConferenceStageResponse response = new ConferenceStageResponse(conference.getStage().ordinal());
        response.setMessage(SUCCESS);
        return responseWrapperUtil.buildResponseBody(response, OK);
    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'paper:pcmember:'+#id)")
    public ResponseEntity<?> getPost(long id) {
        Post post = postService.findByPaperIdNotInRebuttal(id);
        if (null == post) {
            return responseWrapperUtil.buildBaseResponseBody("post not exist", NOT_FOUND);
        }
        PostProjection postProjection = PostProjection.project(post);
        PostResponse postResponse = new PostResponse(postProjection);
        postResponse.setMessage(SUCCESS);
        return responseWrapperUtil.buildResponseBody(postResponse, OK);

    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'conference:chair:'+#id)")
    public ResponseEntity<?> getUnconfirmedPapers(long id, int release) {
        if (release < 0 || release > 1) {
            return responseWrapperUtil.
                    buildBaseResponseBody(ARGUMENTS_ERROR, NOT_ACCEPTABLE);
        }

        List<Paper> paperList;
        paperList = release == 0 ? paperService.findUnconfirmedPapers(id, release)
                : paperService.findUnconfirmedRebuttedPapers(id, release);

        List<UnconfirmedPaperProjection> unconfirmedPaperProjections =
                UnconfirmedPaperProjection.project(Release.values()[release], paperList);
        UnconfirmedGetResponse response = new UnconfirmedGetResponse(unconfirmedPaperProjections);
        response.setMessage(SUCCESS);
        return responseWrapperUtil.buildResponseBody(response, OK);

    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'conference:chair:'+#id)")
    public ResponseEntity<?> getUnconfirmedCount(long id, int release) {
        if (release < 0 || release > 1) {
            return responseWrapperUtil.
                    buildBaseResponseBody(ARGUMENTS_ERROR, NOT_ACCEPTABLE);
        }

        List<Paper> paperList;
        paperList = release == 0 ? paperService.findUnconfirmedPapers(id, release)
                : paperService.findUnconfirmedRebuttedPapers(id, release);

        CountResponse response = new CountResponse(paperList.size());
        response.setMessage(SUCCESS);
        return responseWrapperUtil.buildResponseBody(response, OK);
    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'conference:pcmember:'+#id)")
    public ResponseEntity<?> getRebuttedPapers(long id, boolean confirmed) {
        List<Rebuttal> rebuttals = rebuttalService.
                findRebuttalsForReviewer(id, userDetailsUtil.currentUser(), confirmed);
        PaperResourceResponse paperResourceResponse = new PaperResourceResponse(
                PaperProjection.projectRebuttals(rebuttals));
        return responseWrapperUtil.buildResponseBody(paperResourceResponse, OK);
    }

    @PreAuthorize("hasAuthority('USER') and hasPermission(null,'paper:pcmember:'+#id)")
    public ResponseEntity<?> getRebuttal(long id) {
        Rebuttal rebuttal = rebuttalService.findByPaperId(id);
        if (rebuttal == null) {
            return responseWrapperUtil.
                    buildBaseResponseBody("rebuttal not found", NOT_FOUND);
        }

        RebuttalProjection rebuttalProjection = RebuttalProjection.project(rebuttal);
        RebuttalResponse rebuttalResponse = new RebuttalResponse(rebuttalProjection);
        rebuttalResponse.setMessage(SUCCESS);
        return responseWrapperUtil.buildResponseBody(rebuttalResponse, OK);


    }
}
